package com.xxx.springboot.util;

import com.alibaba.fastjson.JSON;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.net.URLEncoder;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;

/**
 * TuShare获取股票信息工具类
 */
public class PythonUtil {

    private static final Logger logger = LoggerFactory.getLogger(PythonUtil.class);

    private static final String PYTHON_URL_LOCAL = "http://127.0.0.1:7000";
    private static final String PYTHON_URL_PROD = "http://172.26.214.244/pyapi";
    private static final String PYTHON_URL_PYTHON_WEB = "http://172.26.214.244:7000";
    private static final String PYTHON_URL_PYTHON_ADMIN = "http://172.26.214.245:7000";

    private static final String PYTHON_URL_STRATEGY_LOCAL = "http://127.0.0.1:5000";
    private static final String PYTHON_URL_STRATEGY_PROD = "http://172.26.214.244/strategyapi";
    private static final String PYTHON_URL_BACKTEST_1 = "http://172.26.214.244/backtest1";
    private static final String PYTHON_URL_BACKTEST_2 = "http://172.26.214.244/backtest2";

    // 使用nginx负载
    private static String getPythonUrl(boolean isProd) {
        if(isProd) {
            return PYTHON_URL_PROD;
        }
        return PYTHON_URL_LOCAL;
    }

    // 按照股票代码尾号进行负载
//    private static String getPythonUrlByTsCode(boolean isProd, String tsCode) {
//        if(isProd) {
//            // 使用股票代码进行负载均衡
//            if(CommonUtil.checkStockCodeIsEven(tsCode)) {
//                return PYTHON_URL_PYTHON_WEB;
//            } else {
//                return PYTHON_URL_PYTHON_ADMIN;
//            }
//        }
//        return PYTHON_URL_LOCAL;
//    }

    // 随机负载
    private static String getPythonUrl4BacktestByRandom(boolean isProd) {
        if(isProd) {
            Random r = new Random();
            int ran = r.nextInt(10);
            if (ran % 2 == 0) {
                return PYTHON_URL_BACKTEST_2;
            } else {
                return PYTHON_URL_BACKTEST_1;
            }
        }
        return PYTHON_URL_STRATEGY_LOCAL;
    }

    // 按照股票代码尾号进行负载
    private static String getPythonUrl4Backtest(boolean isProd, String tsCode) {
        if(isProd) {
            // 使用股票代码进行负载均衡
            if(CommonUtil.checkStockCodeIsEven(tsCode)) {
                return PYTHON_URL_BACKTEST_2;
            } else {
                return PYTHON_URL_BACKTEST_1;
            }
        }
        return PYTHON_URL_STRATEGY_LOCAL;
    }

    /**
     * 批量获取基金的基金经理
     * @param isProd
     * @param ts_code_list
     * @return
     */
    public static Map<String, String> batchGetFundManager(boolean isProd, List<String> ts_code_list) {
        if(CollectionUtils.isEmpty(ts_code_list)) {
            throw new RuntimeException("ts_code_list 不能为空");
        }

        Map<String, String> result = new HashMap<>();
        try {
            String ts_code = StringUtils.join(ts_code_list, ",");
            String url = getPythonUrl(isProd);
            url = url + "/get_fund_manager?ts_code=" + ts_code;
            logger.info("batchGetFundManager|url:{}", url);
            String resJson = HttpClientUtil.doGet(url);
            Map maps = (Map) JSON.parse(resJson);
            for (Object map : maps.entrySet()){
                if(map!=null && ((Map.Entry)map).getKey()!=null && ((Map.Entry)map).getValue()!=null) {
                    result.put((String)((Map.Entry)map).getKey(), (String)((Map.Entry)map).getValue());
                }
            }
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
        }
        return result;
    }

    /**
     * 获取基金最新净值
     * @param isProd
     * @param ts_code
     * @return
     */
    public static String getFundLatestNav(boolean isProd, String ts_code) {
        if(StringUtils.isEmpty(ts_code)) {
            throw new RuntimeException("ts_code 不能为空");
        }

        try {
            String url = getPythonUrl(isProd);
            url = url + "/get_fund_latest_nav?ts_code=" + ts_code;
            logger.info("getFundLatestNav|url:{}", url);
            return HttpClientUtil.doGet(url);
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
        }
        return null;
    }


    /**
     * 获取基金持仓股票（后台）
     * @param isProd
     * @param ts_code
     * @return
     */
    public static String getFundPosition(boolean isProd, String ts_code) {
        if(StringUtils.isEmpty(ts_code)) {
            throw new RuntimeException("ts_code 不能为空");
        }

        try {
            String url = getPythonUrl(isProd);
            url = url + "/get_fund_position?ts_code=" + ts_code;
            logger.info("getFundPosition|url:{}", url);
            return HttpClientUtil.doGet(url);
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
        }
        return null;
    }

    /**
     * 获取基金持仓股票（前台）
     * @param isProd
     * @param ts_code
     * @return
     */
    public static String getFundPosition2(boolean isProd, String ts_code) {
        if(StringUtils.isEmpty(ts_code)) {
            throw new RuntimeException("ts_code 不能为空");
        }

        try {
            String url = getPythonUrl(isProd);
            url = url + "/get_fund_position2?ts_code=" + ts_code;
            logger.info("getFundPosition2|url:{}", url);
            return HttpClientUtil.doGet(url);
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
        }
        return null;
    }

    /**
     * 获取基金历史业绩
     * @param isProd
     * @param ts_code
     * @return
     */
    public static String getFundAchiveHis(boolean isProd, String ts_code) {
        if(StringUtils.isEmpty(ts_code)) {
            throw new RuntimeException("ts_code 不能为空");
        }

        try {
            String url = getPythonUrl(isProd);
            url = url + "/get_fund_achive_his?ts_code=" + ts_code;
            logger.info("getFundAchiveHis|url:{}", url);
            String resJson =  HttpClientUtil.doGet(url);
            if (!StringUtils.isEmpty(resJson)) {
                resJson = HutoolUtil.unicode2Str(resJson);
            }
            return resJson;
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
        }
        return null;
    }

    /**
     * 获取基金指定周期的业绩表现
     * @param isProd
     * @param ts_code
     * @param interval
     * @return
     */
    public static String getFundIntervalAchive(boolean isProd, String ts_code, String interval) {
        if(StringUtils.isEmpty(ts_code)) {
            throw new RuntimeException("ts_code 不能为空");
        }

        try {
            String url = getPythonUrl(isProd);
            url = url + "/get_fund_interval_achive?ts_code=" + ts_code + "&interval=" + interval;
            logger.info("getFundIntervalAchive|url:{}", url);
            String resJson =  HttpClientUtil.doGet(url);
            if (!StringUtils.isEmpty(resJson)) {
                resJson = HutoolUtil.unicode2Str(resJson);
            }
            return resJson;
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
        }
        return null;
    }

    /**
     * 获取基金所有周期的业绩表现
     * @param isProd
     * @param ts_code
     * @return
     */
    public static String getFundAllIntervalAchive(boolean isProd, String ts_code) {
        if(StringUtils.isEmpty(ts_code)) {
            throw new RuntimeException("ts_code 不能为空");
        }

        try {
            String url = getPythonUrl(isProd);
            url = url + "/get_fund_all_interval_achive?ts_code=" + ts_code;
            logger.info("getFundAllIntervalAchive|url:{}", url);
            String resJson =  HttpClientUtil.doGet(url);
            if (!StringUtils.isEmpty(resJson)) {
                resJson = HutoolUtil.unicode2Str(resJson);
            }
            return resJson;
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
        }
        return null;
    }

    /**
     * 获取基金历史业绩走势
     * @param isProd
     * @param ts_code
     * @param interval
     * @return
     */
    public static String getFundAchiveTrend(boolean isProd, String ts_code, String interval) {
        if(StringUtils.isEmpty(ts_code)) {
            throw new RuntimeException("ts_code 不能为空");
        }

        try {
            String url = getPythonUrl(isProd);
            url = url + "/get_fund_achive_trend?ts_code=" + ts_code + "&interval=" + interval;
            logger.info("getFundAchiveTrend|url:{}", url);
            String resJson =  HttpClientUtil.doGet(url);
            if (!StringUtils.isEmpty(resJson)) {
                resJson = HutoolUtil.unicode2Str(resJson);
            }
            return resJson;
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
        }
        return null;
    }

    /**
     * 获取基金历史市盈率走势
     * @param isProd
     * @param ts_code
     * @param beginDate
     * @param endDate
     * @return
     */
    public static String getFundPeTTMTrend(boolean isProd, String ts_code, String beginDate, String endDate) {
        if(StringUtils.isEmpty(ts_code)) {
            throw new RuntimeException("ts_code 不能为空");
        }

        try {
            String url = getPythonUrl(isProd);
            url = url + "/get_fund_pe_ttm_trend?ts_code=" + ts_code + "&start=" + beginDate + "&end=" + endDate;
            logger.info("getFundPeTTMTrend|url:{}", url);
            String resJson =  HttpClientUtil.doGet(url);
            if (!StringUtils.isEmpty(resJson)) {
                resJson = HutoolUtil.unicode2Str(resJson);
            }
            return resJson;
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
        }
        return null;
    }

    /**
     * 获取股票历史市盈率走势
     * @param isProd
     * @param ts_code
     * @return
     */
    public static String getStockPeTTMTrend(boolean isProd, String ts_code) {
        if(StringUtils.isEmpty(ts_code)) {
            throw new RuntimeException("ts_code 不能为空");
        }

        try {
            String url = getPythonUrl(isProd);
            url = url + "/get_stock_pe_ttm_trend?ts_code=" + ts_code;
            logger.info("getStockPeTTMTrend|url:{}", url);
            String resJson =  HttpClientUtil.doGet(url);
            if (!StringUtils.isEmpty(resJson)) {
                resJson = HutoolUtil.unicode2Str(resJson);
            }
            return resJson;
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
        }
        return null;
    }

    /**
     * 获取股票历史市销率走势
     * @param isProd
     * @param ts_code
     * @return
     */
    public static String getStockPsTTMTrend(boolean isProd, String ts_code) {
        if(StringUtils.isEmpty(ts_code)) {
            throw new RuntimeException("ts_code 不能为空");
        }

        try {
            String url = getPythonUrl(isProd);
            url = url + "/get_stock_ps_ttm_trend?ts_code=" + ts_code;
            logger.info("getStockPsTTMTrend|url:{}", url);
            String resJson =  HttpClientUtil.doGet(url);
            if (!StringUtils.isEmpty(resJson)) {
                resJson = HutoolUtil.unicode2Str(resJson);
            }
            return resJson;
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
        }
        return null;
    }

    /**
     * 获取港股历史市盈率走势
     * @param isProd
     * @param ts_code
     * @return
     */
    public static String getHkStockPeTTMTrend(boolean isProd, String ts_code) {
        if(StringUtils.isEmpty(ts_code)) {
            throw new RuntimeException("ts_code 不能为空");
        }

        try {
            String url = getPythonUrl(isProd);
            url = url + "/get_hk_stock_pe_ttm_trend?ts_code=" + ts_code;
            logger.info("getHkStockPeTTMTrend|url:{}", url);
            String resJson =  HttpClientUtil.doGet(url);
            if (!StringUtils.isEmpty(resJson)) {
                resJson = HutoolUtil.unicode2Str(resJson);
            }
            return resJson;
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
        }
        return null;
    }

    /**
     * 获取港股历史市净率走势
     * @param isProd
     * @param ts_code
     * @return
     */
    public static String getHkStockPbTTMTrend(boolean isProd, String ts_code) {
        if(StringUtils.isEmpty(ts_code)) {
            throw new RuntimeException("ts_code 不能为空");
        }

        try {
            String url = getPythonUrl(isProd);
            url = url + "/get_hk_stock_pb_trend?ts_code=" + ts_code;
            logger.info("getHkStockPbTTMTrend|url:{}", url);
            String resJson =  HttpClientUtil.doGet(url);
            if (!StringUtils.isEmpty(resJson)) {
                resJson = HutoolUtil.unicode2Str(resJson);
            }
            return resJson;
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
        }
        return null;
    }

    /**
     * 获取股票历史价格列表
     * @param isProd
     * @param ts_code
     * @param interval
     * @return
     */
    public static String getStockPriceList(boolean isProd, String ts_code, String interval) {
        if(StringUtils.isEmpty(ts_code)) {
            throw new RuntimeException("ts_code 不能为空");
        }
        if(StringUtils.isEmpty(interval)) {
            throw new RuntimeException("interval 不能为空");
        }

        try {
            String url = getPythonUrl(isProd);
            url = url + "/get_stock_price_list?ts_code=" + ts_code + "&interval=" + interval;
            logger.info("getStockPriceList|url:{}", url);
            String resJson =  HttpClientUtil.doGet(url);
            if (!StringUtils.isEmpty(resJson)) {
                resJson = HutoolUtil.unicode2Str(resJson);
            }
            return resJson;
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
        }
        return null;
    }

    /**
     * 获取指数历史价格列表
     * @param isProd
     * @param index_code
     * @param beginDate
     * @param endDate
     * @param interval
     * @return
     */
    public static String getThsIndexPriceList(boolean isProd, String index_code, String beginDate, String endDate, String interval) {
        if(StringUtils.isEmpty(index_code)) {
            throw new RuntimeException("index_code 不能为空");
        }
        if(StringUtils.isEmpty(interval)) {
            throw new RuntimeException("interval 不能为空");
        }

        try {
            String url = getPythonUrl(isProd);
            url = url + "/get_ths_index_price_list?index_code=" + index_code + "&start=" + beginDate + "&end=" + endDate + "&interval=" + interval;
            logger.info("getThsIndexPriceList|url:{}", url);
            String resJson =  HttpClientUtil.doGet(url);
            if (!StringUtils.isEmpty(resJson)) {
                resJson = HutoolUtil.unicode2Str(resJson);
            }
            return resJson;
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
        }
        return null;
    }

    /**
     * 获取指数历史价格列表
     * @param isProd
     * @param index_code
     * @param beginDate
     * @param endDate
     * @param interval
     * @return
     */
    public static String getIndexPriceList(boolean isProd, String index_code, String beginDate, String endDate, String interval) {
        if(StringUtils.isEmpty(index_code)) {
            throw new RuntimeException("index_code 不能为空");
        }
        if(StringUtils.isEmpty(interval)) {
            throw new RuntimeException("interval 不能为空");
        }

        try {
            String url = getPythonUrl(isProd);
            url = url + "/get_index_price_list?ts_code=" + index_code + "&start=" + beginDate + "&end=" + endDate + "&interval=" + interval;
            logger.info("getIndexPriceList|url:{}", url);
            String resJson =  HttpClientUtil.doGet(url);
            if (!StringUtils.isEmpty(resJson)) {
                resJson = HutoolUtil.unicode2Str(resJson);
            }
            return resJson;
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
        }
        return null;
    }

    /**
     * 获取指数历史收益率列表
     * @param isProd
     * @param index_code
     * @param beginDate
     * @param endDate
     * @param interval
     * @return
     */
    public static String getIndexPrList(boolean isProd, String index_code, String beginDate, String endDate, String interval) {
        if(StringUtils.isEmpty(index_code)) {
            throw new RuntimeException("index_code 不能为空");
        }
        if(StringUtils.isEmpty(interval)) {
            throw new RuntimeException("interval 不能为空");
        }

        try {
            String url = getPythonUrl(isProd);
            url = url + "/get_index_pr_list?ts_code=" + index_code + "&start=" + beginDate + "&end=" + endDate + "&interval=" + interval;
            logger.info("getIndexPrList|url:{}", url);
            String resJson = HttpClientUtil.doGet(url);
            if (!StringUtils.isEmpty(resJson)) {
                resJson = HutoolUtil.unicode2Str(resJson);
            }
            return resJson;
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
        }
        return null;
    }

    /**
     * 获取指数和对比指数收益率列表
     * @param isProd
     * @param index_code
     * @param beginDate
     * @param endDate
     * @return
     */
    public static String getIndexPrListV2(boolean isProd, String index_code, String rel_index_code, String beginDate, String endDate) {
        if(StringUtils.isEmpty(index_code)) {
            throw new RuntimeException("index_code 不能为空");
        }
        if(StringUtils.isEmpty(rel_index_code)) {
            throw new RuntimeException("rel_index_code 不能为空");
        }

        try {
            String url = getPythonUrl(isProd);
            url = url + "/get_index_pr_list_v2?ts_code=" + index_code + "&rel_index_code=" + rel_index_code + "&start=" + beginDate + "&end=" + endDate;
            logger.info("getIndexPrListV2|url:{}", url);
            String resJson = HttpClientUtil.doGet(url);
            if (!StringUtils.isEmpty(resJson)) {
                resJson = HutoolUtil.unicode2Str(resJson);
            }
            return resJson;
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
        }
        return null;
    }

    /**
     * 获取申万指数历史价格列表
     * @param isProd
     * @param index_code
     * @param interval
     * @param start_date
     * @param end_date
     * @return
     */
    public static String getSwIndexPriceList(boolean isProd, String index_code, String interval, String start_date, String end_date) {
        if(StringUtils.isEmpty(index_code)) {
            throw new RuntimeException("index_code 不能为空");
        }
        if(StringUtils.isEmpty(interval)) {
            throw new RuntimeException("interval 不能为空");
        }

        try {
            String url = getPythonUrl(isProd);
            url = url + "/get_sw_index_price_list?index_code=" + index_code + "&interval=" + interval + "&start_date=" + start_date + "&end_date=" + end_date;
            logger.info("getSwIndexPriceList|url:{}", url);
            String resJson =  HttpClientUtil.doGet(url);
            if (!StringUtils.isEmpty(resJson)) {
                resJson = HutoolUtil.unicode2Str(resJson);
            }
            return resJson;
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
        }
        return null;
    }

    /**
     * 获取资产组合历史总资产列表
     * @param isProd
     * @param combId
     * @return
     */
    public static String getCombPrList(boolean isProd, String combId) {
        if(StringUtils.isEmpty(combId)) {
            throw new RuntimeException("ts_code 不能为空");
        }

        try {
            String url = getPythonUrl(isProd);
            url = url + "/get_comb_pr_list?comb_id=" + combId;
            logger.info("getCombPrList|url:{}", url);
            String resJson =  HttpClientUtil.doGet(url);
            if (!StringUtils.isEmpty(resJson)) {
                resJson = HutoolUtil.unicode2Str(resJson);
            }
            return resJson;
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
        }
        return null;
    }

    /**
     * 获取资产组合历史总资产列表
     * @param isProd
     * @param combId
     * @return
     */
    public static String getCombPrListV2(boolean isProd, String combId, String indexCode, String calcType, String startDate, String endDate) {
        if(StringUtils.isEmpty(combId)) {
            throw new RuntimeException("ts_code 不能为空");
        }

        try {
            String url = getPythonUrl(isProd);
            // http://127.0.0.1:7000/get_comb_pr_list_v2?comb_id=76993232870821111&index_code=000300.SH&calc_type=ss&start_date=2023-01-03&end_date=2024-03-07
            url = url + "/get_comb_pr_list_v2?comb_id=" + combId + "&index_code=" + indexCode + "&calc_type=" + calcType + "&start_date=" + startDate + "&end_date=" + endDate;
            logger.info("getCombPrListV2|url:{}", url);
            String resJson =  HttpClientUtil.doGet(url);
            if (!StringUtils.isEmpty(resJson)) {
                resJson = HutoolUtil.unicode2Str(resJson);
            }
            return resJson;
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
        }
        return null;
    }

    /**
     * 获取申万行业指数历史市盈率走势
     * @param isProd
     * @param index_code
     * @param type
     * @return
     */
    public static String getSwIndexPeTrend(boolean isProd, String index_code, String type) {
        if(StringUtils.isEmpty(index_code)) {
            throw new RuntimeException("index_code 不能为空");
        }

        try {
            String url = getPythonUrl(isProd);
            url = url + "/get_sw_index_pe_trend?index_code=" + index_code + "&type=" + type;
            logger.info("getSwIndexPeTrend|url:{}", url);
            String resJson =  HttpClientUtil.doGet(url);
            if (!StringUtils.isEmpty(resJson)) {
                resJson = HutoolUtil.unicode2Str(resJson);
            }
            return resJson;
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
        }
        return null;
    }

    /**
     * 获取同花顺指数历史市盈率走势
     * @param isProd
     * @param index_code
     * @param type
     * @return
     */
    public static String getThsIndexPeTrend(boolean isProd, String index_code, String type) {
        if(StringUtils.isEmpty(index_code)) {
            throw new RuntimeException("index_code 不能为空");
        }

        try {
            String url = getPythonUrl(isProd);
            url = url + "/get_ths_index_pe_trend?index_code=" + index_code + "&type=" + type;
            logger.info("getThsIndexPeTrend|url:{}", url);
            String resJson =  HttpClientUtil.doGet(url);
            if (!StringUtils.isEmpty(resJson)) {
                resJson = HutoolUtil.unicode2Str(resJson);
            }
            return resJson;
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
        }
        return null;
    }

    /**
     * 获取几大指数历史市盈率走势
     * @param isProd
     * @param index_code
     * @param type
     * @return
     */
    public static String getIndexPeTrend(boolean isProd, String index_code, String type) {
        if(StringUtils.isEmpty(index_code)) {
            throw new RuntimeException("index_code 不能为空");
        }

        try {
            String url = getPythonUrl(isProd);
            url = url + "/get_index_pe_trend?index_code=" + index_code + "&type=" + type;
            logger.info("getIndexPeTrend|url:{}", url);
            String resJson =  HttpClientUtil.doGet(url);
            if (!StringUtils.isEmpty(resJson)) {
                resJson = HutoolUtil.unicode2Str(resJson);
            }
            return resJson;
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
        }
        return null;
    }

    /**
     * 获取基金历史市盈率走势
     * @param isProd
     * @param ts_code
     * @return
     */
    public static String getFundReturnList(boolean isProd, String ts_code) {
        if(StringUtils.isEmpty(ts_code)) {
            throw new RuntimeException("ts_code 不能为空");
        }

        try {
            String url = getPythonUrl(isProd);
            url = url + "/get_fund_return_list?ts_code=" + ts_code;
            logger.info("getFundReturnList|url:{}", url);
            String resJson =  HttpClientUtil.doGet(url);
            if (!StringUtils.isEmpty(resJson)) {
                resJson = HutoolUtil.unicode2Str(resJson);
            }
            return resJson;
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
        }
        return null;
    }

    /**
     * 获取基金回测买入卖出记录
     * @param isProd
     * @param ts_code
     * @return
     */
    public static String getBuysellLogList(boolean isProd, String ts_code) {
        if(StringUtils.isEmpty(ts_code)) {
            throw new RuntimeException("ts_code 不能为空");
        }

        try {
            String url = getPythonUrl(isProd);
            url = url + "/get_fund_buysell_log_list?ts_code=" + ts_code;
            logger.info("getBuysellLogList|url:{}", url);
            String resJson =  HttpClientUtil.doGet(url);
            if (!StringUtils.isEmpty(resJson)) {
                resJson = HutoolUtil.unicode2Str(resJson);
            }
            return resJson;
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
        }
        return null;
    }

    /**
     * 获取收藏的股票+策略的自然收益率、策略收益率、交易次数
     * @param isProd
     * @param stockCode_strategyId_list
     * @return
     */
    public static String getCollectStockStrategyProfitList(boolean isProd, String stockCode_strategyId_list) {
        if(StringUtils.isEmpty(stockCode_strategyId_list)) {
            throw new RuntimeException("stockCode_strategyId_list 不能为空");
        }

        try {
            String url = getPythonUrl(isProd);
            // URL转义
            String encodeUrl = URLEncoder.encode(stockCode_strategyId_list, "utf-8");
            url = url + "/get_collect_stock_strategy_profit_list?stockCode_strategyId_list=" + encodeUrl;
            logger.info("getCollectStockStrategyProfitList|url:{}", url);
            String resJson =  HttpClientUtil.doGet(url);
            if (!StringUtils.isEmpty(resJson)) {
                resJson = HutoolUtil.unicode2Str(resJson);
            }
            return resJson;
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
        }
        return null;
    }

    /**
     * 获取股票近1年/1月/1季的自然收益率、收益排名
     * @param isProd
     * @param stockTsCodeList
     * @return
     */
    public static String getStockLatest1YearProfitRatio(boolean isProd, String interval, String stockTsCodeList) {
        if(StringUtils.isEmpty(interval)) {
            throw new RuntimeException("interval 不能为空");
        }
        if(StringUtils.isEmpty(stockTsCodeList)) {
            throw new RuntimeException("stockTsCodeList 不能为空");
        }

        try {
            String url = getPythonUrl(isProd);
            url = url + "/get_stock_profit_ratio_list?interval=" + interval+ "&ts_code_list=" + stockTsCodeList;
            logger.info("getStockLatest1YearProfitRatio|url:{}", url);
            String resJson =  HttpClientUtil.doGet(url);
            if (!StringUtils.isEmpty(resJson)) {
                resJson = HutoolUtil.unicode2Str(resJson);
            }
            return resJson;
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
        }
        return null;
    }

    /**
     * 批量获取股票指定区间的累计收益率
     * @param isProd
     * @param stockTsCodeList
     * @param startDate
     * @param endDate
     * @return
     */
    public static String batchGetStockPrList(boolean isProd, String stockTsCodeList, String startDate, String endDate) {
        if(StringUtils.isEmpty(stockTsCodeList)) {
            throw new RuntimeException("stockTsCodeList 不能为空");
        }
        if(StringUtils.isEmpty(startDate)) {
            throw new RuntimeException("startDate 不能为空");
        }
        if(StringUtils.isEmpty(endDate)) {
            throw new RuntimeException("endDate 不能为空");
        }

        try {
            String url = getPythonUrl(isProd);
            url = url + "/batch_get_stock_pr_list?ts_code_list=" + stockTsCodeList + "&start_date=" + startDate + "&end_date=" + endDate;
            logger.info("batchGetStockPrList|url:{}", url);
            String resJson =  HttpClientUtil.doGet(url);
            if (!StringUtils.isEmpty(resJson)) {
                resJson = HutoolUtil.unicode2Str(resJson);
            }
            return resJson;
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
        }
        return null;
    }

    /**
     * 获取基本面策略的自然收益率、收益排名
     * @param isProd
     * @param strategyId
     * @param strategyParam
     * @param stockTsCodeList
     * @return
     */
    public static String getStockFundamentalProfitRatio(boolean isProd, String strategyId, String strategyParam, String stockTsCodeList) {
        if(StringUtils.isEmpty(strategyId)) {
            throw new RuntimeException("strategyId 不能为空");
        }
        if(StringUtils.isEmpty(strategyParam)) {
            throw new RuntimeException("strategyParam 不能为空");
        }
        if(StringUtils.isEmpty(stockTsCodeList)) {
            throw new RuntimeException("stockTsCodeList 不能为空");
        }

        try {
            String url = getPythonUrl(isProd);
            url = url + "/get_stock_fundamental_profit_ratio?strategy_id=" + strategyId + "&strategy_param=" + strategyParam + "&ts_code_list=" + stockTsCodeList;
            logger.info("getStockFundamentalProfitRatio|url:{}", url);
            String resJson =  HttpClientUtil.doGet(url);
            if (!StringUtils.isEmpty(resJson)) {
                resJson = HutoolUtil.unicode2Str(resJson);
            }
            return resJson;
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
        }
        return null;
    }

    /**
     * 获取技术面策略的自然收益率、策略收益率、收益排名
     * @param isProd
     * @param strategyId
     * @param strategyParam
     * @param stockTsCodeList
     * @return
     */
    public static String getStockTechnicalProfitRatio(boolean isProd, String strategyId, String strategyParam, String stockTsCodeList) {
        if(StringUtils.isEmpty(strategyId)) {
            throw new RuntimeException("strategyId 不能为空");
        }
        if(StringUtils.isEmpty(strategyParam)) {
            throw new RuntimeException("strategyParam 不能为空");
        }
        if(StringUtils.isEmpty(stockTsCodeList)) {
            throw new RuntimeException("stockTsCodeList 不能为空");
        }

        try {
            String url = getPythonUrl(isProd);
            url = url + "/get_stock_technical_profit_ratio?strategy_id=" + strategyId + "&strategy_param=" + strategyParam + "&ts_code_list=" + stockTsCodeList;
            logger.info("getStockTechnicalProfitRatio|url:{}", url);
            String resJson =  HttpClientUtil.doGet(url);
            if (!StringUtils.isEmpty(resJson)) {
                resJson = HutoolUtil.unicode2Str(resJson);
            }
            return resJson;
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
        }
        return null;
    }

    /**
     * 获取技术面策略股票和收益率
     * @param isProd
     * @param strategyId
     * @param strategyParam
     * @param stockTsCode
     * @param holdStatus
     * @return
     */
    public static String getTechnicalStockList(boolean isProd, String strategyId, String strategyParam, String stockTsCode, String holdStatus) {
        if(StringUtils.isEmpty(strategyId)) {
            throw new RuntimeException("strategyId 不能为空");
        }
        if(StringUtils.isEmpty(strategyParam)) {
            throw new RuntimeException("strategyParam 不能为空");
        }

        try {
            String url = getPythonUrl(isProd);
            url = url + "/get_technical_stock_list?strategy_id=" + strategyId + "&strategy_param=" + strategyParam + "&ts_code=" + stockTsCode + "&hold_status=" + holdStatus;
            logger.info("getTechnicalStockList|url:{}", url);
            String resJson =  HttpClientUtil.doGet(url);
            if (!StringUtils.isEmpty(resJson)) {
                resJson = HutoolUtil.unicode2Str(resJson);
            }
            return resJson;
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
        }
        return null;
    }

    /**
     * 获取指数收益率
     * @param isProd
     * @param index_code
     * @param start_date
     * @param end_date
     * @return
     */
    public static String getIndexPr(boolean isProd, String index_code, String start_date, String end_date) {
        if(StringUtils.isEmpty(index_code)) {
            throw new RuntimeException("index_code 不能为空");
        }
        if(StringUtils.isEmpty(start_date)) {
            throw new RuntimeException("start_date 不能为空");
        }
        if(StringUtils.isEmpty(end_date)) {
            throw new RuntimeException("end_date 不能为空");
        }

        try {
            String url = getPythonUrl(isProd);
            url = url + "/get_index_pr?index_code=" + index_code + "&start_date=" + start_date + "&end_date=" + end_date;
            logger.info("getIndexPr|url:{}", url);
            String resJson =  HttpClientUtil.doGet(url);
            if (!StringUtils.isEmpty(resJson)) {
                resJson = HutoolUtil.unicode2Str(resJson);
            }
            return resJson;
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
        }
        return null;
    }

    /**
     * 获取申万指数收益率
     * @param isProd
     * @param index_code
     * @param start_date
     * @param end_date
     * @return
     */
    public static String getSwIndexPr(boolean isProd, String index_code, String start_date, String end_date) {
        if(StringUtils.isEmpty(index_code)) {
            throw new RuntimeException("index_code 不能为空");
        }
        if(StringUtils.isEmpty(start_date)) {
            throw new RuntimeException("start_date 不能为空");
        }
        if(StringUtils.isEmpty(end_date)) {
            throw new RuntimeException("end_date 不能为空");
        }

        try {
            String url = getPythonUrl(isProd);
            url = url + "/get_sw_index_pr?index_code=" + index_code + "&start_date=" + start_date + "&end_date=" + end_date;
            logger.info("getSwIndexPr|url:{}", url);
            String resJson =  HttpClientUtil.doGet(url);
            if (!StringUtils.isEmpty(resJson)) {
                resJson = HutoolUtil.unicode2Str(resJson);
            }
            return resJson;
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
        }
        return null;
    }

    /**
     * 发起回测
     */
    public static String runBackTest(boolean isProd, String tsCode, String strategyId, String strategyParam) {
        logger.info("runBackTest|tsCode:{}, strategyId:{}, strategyParam:{}", tsCode, strategyId, strategyParam);
        if(StringUtils.isEmpty(tsCode)) {
            throw new RuntimeException("tsCode 不能为空");
        }
        if(StringUtils.isEmpty(strategyId)) {
            throw new RuntimeException("strategyId 不能为空");
        }
        if(StringUtils.isEmpty(strategyParam)) {
            throw new RuntimeException("strategyParam 不能为空");
        }

        try {
            String url = getPythonUrl4Backtest(isProd, tsCode);
            url = url + "/run_back_test?strategy_id=" + strategyId + "&strategy_param=" + strategyParam + "&ts_code=" + tsCode;
            logger.info("runBackTest|url:{}", url);
            return HttpClientUtil.doGet(url);
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
        }
        return null;
    }

    /**
     * 获取回测结果买入卖出记录
     */
    public static String getBackTestBuyselllog(boolean isProd, String tsCode, String strategyId, String strategyParam) {
        logger.info("getBackTestBuyselllog|tsCode:{}, strategyId:{}, strategyParam:{}", tsCode, strategyId, strategyParam);
        if(StringUtils.isEmpty(tsCode)) {
            throw new RuntimeException("tsCode 不能为空");
        }
        if(StringUtils.isEmpty(strategyId)) {
            throw new RuntimeException("strategyId 不能为空");
        }
        if(StringUtils.isEmpty(strategyParam)) {
            throw new RuntimeException("strategyParam 不能为空");
        }

        try {
            String url = getPythonUrl4Backtest(isProd, tsCode);
            url = url + "/get_back_test_buyselllog?strategy_id=" + strategyId + "&strategy_param=" + strategyParam + "&ts_code=" + tsCode;
            logger.info("getBackTestBuyselllog|url:{}", url);
            String resJson =  HttpClientUtil.doGet(url);
            if (!StringUtils.isEmpty(resJson)) {
                resJson = HutoolUtil.unicode2Str(resJson);
            }
            return resJson;
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
        }
        return null;
    }

    /**
     * 获取回测结果指标
     */
    public static String getBackTestResult(boolean isProd, String tsCode, String strategyId, String strategyParam) {
        logger.info("getBackTestResult|tsCode:{}, strategyId:{}, strategyParam:{}", tsCode, strategyId, strategyParam);
        if(StringUtils.isEmpty(tsCode)) {
            throw new RuntimeException("tsCode 不能为空");
        }
        if(StringUtils.isEmpty(strategyId)) {
            throw new RuntimeException("strategyId 不能为空");
        }
        if(StringUtils.isEmpty(strategyParam)) {
            throw new RuntimeException("strategyParam 不能为空");
        }

        try {
            String url = getPythonUrl4Backtest(isProd, tsCode);
            url = url + "/get_back_test_result?strategy_id=" + strategyId + "&strategy_param=" + strategyParam + "&ts_code=" + tsCode;
            logger.info("getBackTestResult|url:{}", url);
            String resJson =  HttpClientUtil.doGet(url);
            if (!StringUtils.isEmpty(resJson)) {
                resJson = HutoolUtil.unicode2Str(resJson);
            }
            return resJson;
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
        }
        return null;
    }

    /**
     * 获取回测资金曲线
     */
    public static String getBackTestEquityCurveList(boolean isProd,
                                                    String tsCode, String strategyId, String strategyParam, String paramIsOptional,
                                                    String collectBeginDate, String collectEndDate, String collectStatus) {
        logger.info("getBackTestEquityCurveList|tsCode:{}, strategyId:{}, strategyParam:{}, paramIsOptional: {}, collectBeginDate:{}, collectEndDate:{}, collectStatus:{}",
                tsCode, strategyId, strategyParam, paramIsOptional, collectBeginDate, collectEndDate, collectStatus);
        if(StringUtils.isEmpty(tsCode)) {
            throw new RuntimeException("tsCode 不能为空");
        }
        if(StringUtils.isEmpty(strategyId)) {
            throw new RuntimeException("strategyId 不能为空");
        }
        if(StringUtils.isEmpty(strategyParam)) {
            throw new RuntimeException("strategyParam 不能为空");
        }

        try {
            String url = getPythonUrl4Backtest(isProd, tsCode);
            url = url + "/get_back_test_equity_curve_list?strategy_id=" + strategyId + "&strategy_param=" + strategyParam + "&param_is_optional=" + paramIsOptional + "&ts_code=" + tsCode + "&collect_begin_date=" + collectBeginDate + "&collect_end_date=" + collectEndDate + "&collect_status=" + collectStatus;
            logger.info("getBackTestEquityCurveList|url:{}", url);
            String resJson =  HttpClientUtil.doGet(url);
            if (!StringUtils.isEmpty(resJson)) {
                resJson = HutoolUtil.unicode2Str(resJson);
            }
            return resJson;
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
        }
        return null;
    }

    /**
     * 获取爬取数据最新日期
     * @param isProd
     * @return
     */
    public static String getJobDataDate(boolean isProd, String fundCode, String stockCode) {
        try {
            String url = getPythonUrl(isProd);
            url = url + "/get_job_info?fund_code="+fundCode+"&stock_code="+stockCode;
            logger.info("getJobDataDate|url:{}", url);
            return HttpClientUtil.doGet(url);
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
        }
        return null;
    }

    /**
     * 获取股票今日开盘 + 实时价格
     * @param isProd
     * @return
     */
    public static String getStockOpenAndRealTimePrice(boolean isProd, String tsCode) {
        if(StringUtils.isEmpty(tsCode)) {
            throw new RuntimeException("tsCode 不能为空");
        }

        String stockCode = "";
        if(tsCode.endsWith("SH")){
            stockCode = "sh" + tsCode.replace(".SH", "");
        } else if(tsCode.endsWith("SZ")) {
            stockCode = "sz" + tsCode.replace(".SZ", "");
        } else if(tsCode.endsWith("BJ")) {
            stockCode = "bj" + tsCode.replace(".BJ", "");
        } else if(tsCode.endsWith("HK")) {
            stockCode = "hk" + tsCode.replace(".HK", "");
        }

        try {
            String url = getPythonUrl(isProd);
            url = url + "/get_stock_open_and_realtime_price?stock_code="+stockCode;
            logger.info("getStockOpenAndRealTimePrice|url:{}", url);
            return HttpClientUtil.doGet(url);
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
        }
        return null;
    }

    /**
     * 获取基金实今日开盘 + 时估算净值
     * @param isProd
     * @return
     */
    public static String getFundOpenAndRealTimeNav(boolean isProd, String fundCode) {
        if(StringUtils.isEmpty(fundCode)) {
            throw new RuntimeException("fundCode 不能为空");
        }

        try {
            String url = getPythonUrl(isProd);
            url = url + "/get_fund_open_and_realtime_nav?fund_code="+fundCode;
            logger.info("getFundOpenAndRealTimeNav|url:{}", url);
            return HttpClientUtil.doGet(url);
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
        }
        return null;
    }

    /**
     * 获取汇率
     * @param isProd
     * @return
     */
    public static String getExchangeRate(boolean isProd, String from_coin, String to_coin) {
        if(StringUtils.isEmpty(from_coin) || StringUtils.isEmpty(to_coin)) {
            throw new RuntimeException("from_coin 和 to_coin");
        }

        try {
            String url = getPythonUrl(isProd);
            url = url + "/get_exchange_rate?from_coin="+from_coin+"&to_coin="+to_coin;
            logger.info("getExchangeRate|url:{}", url);
            return HttpClientUtil.doGet(url);
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
        }
        return null;
    }

    /**
     * 判断A股今日是否开盘
     * @param isProd
     * @return
     */
    public static String checkIsOpen(boolean isProd, String trade_date) {
        if(StringUtils.isEmpty(trade_date)) {
            throw new RuntimeException("trade_date 不能为空");
        }

        try {
            String url = getPythonUrl(isProd);
            url = url + "/check_is_open?trade_date="+trade_date;
            logger.info("checkIsOpen|url:{}", url);
            return HttpClientUtil.doGet(url);
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
        }
        return null;
    }

    /**
     * 判断港股今日是否开盘
     * @param isProd
     * @return
     */
    public static String checkIsHkOpen(boolean isProd, String trade_date) {
        if(StringUtils.isEmpty(trade_date)) {
            throw new RuntimeException("trade_date 不能为空");
        }

        try {
            String url = getPythonUrl(isProd);
            url = url + "/check_is_hk_open?trade_date="+trade_date;
            logger.info("checkIsHkOpen|url:{}", url);
            return HttpClientUtil.doGet(url);
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
        }
        return null;
    }

    /**
     * 判断美股今日是否开盘
     * @param isProd
     * @return
     */
    public static String checkIsUsOpen(boolean isProd, String trade_date) {
        if(StringUtils.isEmpty(trade_date)) {
            throw new RuntimeException("trade_date 不能为空");
        }

        try {
            String url = getPythonUrl(isProd);
            url = url + "/check_is_us_open?trade_date="+trade_date;
            logger.info("checkIsUsOpen|url:{}", url);
            return HttpClientUtil.doGet(url);
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
        }
        return null;
    }

    /**
     * 获取A股最近一个交易日
     * @param isProd
     * @return
     */
    public static String getLastTradeDate(boolean isProd) {

        try {
            String url = getPythonUrl(isProd);
            url = url + "/get_last_trade_date";
            logger.info("getLastTradeDate|url:{}", url);
            return HttpClientUtil.doGet(url);
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
        }
        return null;
    }

    /**
     * 获取A股指定日期的前一个交易日
     * @param isProd
     * @return
     */
    public static String getLastTradeDateByDate(boolean isProd, String date) {

        try {
            String url = getPythonUrl(isProd);
            url = url + "/get_last_trade_date_by_date?date="+date;
            logger.info("getLastTradeDateByDate|url:{}", url);
            return HttpClientUtil.doGet(url);
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
        }
        return null;
    }

    /**
     * 刷新资产组合持仓占比
     * @param isProd
     * @param combId
     * @return
     */
    public static String refreshCombPosRatio(boolean isProd, String combId) {
        if(StringUtils.isEmpty(combId)) {
            throw new RuntimeException("combId 不能为空");
        }

        try {
            String url = getPythonUrl(isProd);
            url = url + "/refresh_comb_pos_ratio?comb_id=" + combId;
            logger.info("refreshCombPosRatio|url:{}", url);
            String resJson =  HttpClientUtil.doGet(url);
            if (!StringUtils.isEmpty(resJson)) {
                resJson = HutoolUtil.unicode2Str(resJson);
            }
            return resJson;
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
        }
        return null;
    }

    /**
     * 计算股票在指定期间的相关指标变化
     * @param isProd
     * @return
     */
    public static String calcStockPctChg(boolean isProd, List<String> tsCodeList, String start, String end) {
        if(CollectionUtils.isEmpty(tsCodeList) || StringUtils.isEmpty(start) || StringUtils.isEmpty(end)) {
            throw new RuntimeException("tsCodeList、start、end 都不能为空");
        }

        // 将tsCodeList用半角逗号连接
        String ts_code_list = StringUtils.join(tsCodeList, ",");

        try {
            String url = getPythonUrl(isProd);
            url = url + "/calc_stock_pct_chg?ts_code_list=" + ts_code_list + "&start=" + start + "&end=" + end;
            logger.info("calcStockPctChg|url:{}", url);
            String resJson =  HttpClientUtil.doGet(url);
            if (!StringUtils.isEmpty(resJson)) {
                resJson = HutoolUtil.unicode2Str(resJson);
            }
            return resJson;
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
        }
        return null;
    }

    /**
     * 获取因子详情
     * @param isProd
     * @param factor
     * @param stockPool
     * @param benchmark
     * @param isSwIndex
     * @param rebalancePeriod
     * @param weight
     * @param startDate
     * @param endDate
     * @return
     */
    public static String getFactorDetail(boolean isProd, String factor, String stockPool, String benchmark,
                                         boolean isSwIndex, Short rebalancePeriod,
                                         String weight, String startDate, String endDate) {
        if(StringUtils.isEmpty(factor)) {
            throw new RuntimeException("factor 不能为空");
        }
        if(StringUtils.isEmpty(stockPool)) {
            throw new RuntimeException("stockPool 不能为空");
        }
        if(StringUtils.isEmpty(benchmark)) {
            throw new RuntimeException("benchmark 不能为空");
        }
        if(StringUtils.isEmpty(weight)) {
            throw new RuntimeException("weight 不能为空");
        }
        if(StringUtils.isEmpty(startDate)) {
            throw new RuntimeException("startDate 不能为空");
        }
        if(StringUtils.isEmpty(endDate)) {
            throw new RuntimeException("endDate 不能为空");
        }
        int is_sw = 0;
        if(isSwIndex) {
            is_sw = 1;
        }

        try {
            String url = getPythonUrl(isProd);
            url = url + "/get_factor_detail?factor=" + factor + "&stock_pool=" + stockPool + "&benchmark=" + benchmark + "&is_sw=" + is_sw + "&rebalance_period=" + rebalancePeriod + "&weight=" + weight + "&start_date=" + startDate + "&end_date=" + endDate;
            logger.info("getFactorDetail|url:{}", url);
            String resJson =  HttpClientUtil.doGet(url);
            if (!StringUtils.isEmpty(resJson)) {
                resJson = HutoolUtil.unicode2Str(resJson);
            }
            return resJson;
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
        }
        return null;
    }

    /**
     * 获取因子收益率曲线
     * @param isProd
     * @param factor
     * @param stockPool
     * @param benchmark
     * @param isSwIndex
     * @param rebalancePeriod
     * @param weight
     * @param startDate
     * @param endDate
     * @param prType
     * @return
     */
    public static String getFactorPrList(boolean isProd, String factor, String stockPool, String benchmark,
                                         boolean isSwIndex, Short rebalancePeriod,
                                         String weight, String startDate, String endDate, Short prType) {
        if(StringUtils.isEmpty(factor)) {
            throw new RuntimeException("factor 不能为空");
        }
        if(StringUtils.isEmpty(stockPool)) {
            throw new RuntimeException("stockPool 不能为空");
        }
        if(StringUtils.isEmpty(benchmark)) {
            throw new RuntimeException("benchmark 不能为空");
        }
        if(StringUtils.isEmpty(weight)) {
            throw new RuntimeException("weight 不能为空");
        }
        if(StringUtils.isEmpty(startDate)) {
            throw new RuntimeException("startDate 不能为空");
        }
        if(StringUtils.isEmpty(endDate)) {
            throw new RuntimeException("endDate 不能为空");
        }
        if(prType==null) {
            throw new RuntimeException("prType 不能为空");
        }
        int is_sw = 0;
        if(isSwIndex) {
            is_sw = 1;
        }

        try {
            String url = getPythonUrl(isProd);
            url = url + "/get_factor_pr_list?factor=" + factor + "&stock_pool=" + stockPool + "&benchmark=" + benchmark + "&is_sw=" + is_sw + "&rebalance_period=" + rebalancePeriod + "&weight=" + weight + "&start_date=" + startDate + "&end_date=" + endDate + "&pr_type=" + prType;
            logger.info("getFactorPrList|url:{}", url);
            String resJson =  HttpClientUtil.doGet(url);
            if (!StringUtils.isEmpty(resJson)) {
                resJson = HutoolUtil.unicode2Str(resJson);
            }
            return resJson;
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
        }
        return null;
    }

    /**
     * 获取行业IC列表
     * @param isProd
     * @param factor
     * @param rebalancePeriod
     * @param weight
     * @param startDate
     * @param endDate
     * @return
     */
    public static String getIndustryIcList(boolean isProd, String factor, Short rebalancePeriod,
                                         String weight, String startDate, String endDate) {
        if(StringUtils.isEmpty(factor)) {
            throw new RuntimeException("factor 不能为空");
        }
        if(StringUtils.isEmpty(weight)) {
            throw new RuntimeException("weight 不能为空");
        }
        if(StringUtils.isEmpty(startDate)) {
            throw new RuntimeException("startDate 不能为空");
        }
        if(StringUtils.isEmpty(endDate)) {
            throw new RuntimeException("endDate 不能为空");
        }

        try {
            String url = getPythonUrl(isProd);
            url = url + "/get_industry_ic_list?factor=" + factor + "&rebalance_period=" + rebalancePeriod + "&weight=" + weight + "&start_date=" + startDate + "&end_date=" + endDate;
            logger.info("getIndustryIcList|url:{}", url);
            String resJson =  HttpClientUtil.doGet(url);
            if (!StringUtils.isEmpty(resJson)) {
                resJson = HutoolUtil.unicode2Str(resJson);
            }
            return resJson;
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
        }
        return null;
    }

    /**
     * 获取因子详情
     * @param isProd
     * @param factor
     * @param stockPool
     * @return
     */
    public static String getFactorStockNum(boolean isProd, String factor, String stockPool, String endDate) {
        if(StringUtils.isEmpty(factor)) {
            throw new RuntimeException("factor 不能为空");
        }
        if(StringUtils.isEmpty(stockPool)) {
            throw new RuntimeException("stockPool 不能为空");
        }
        if(StringUtils.isEmpty(endDate)) {
            throw new RuntimeException("endDate 不能为空");
        }

        try {
            String url = getPythonUrl4BacktestByRandom(isProd);
            url = url + "/get_factor_stock_num?factor=" + factor + "&stock_pool=" + stockPool + "&end_date=" + endDate;
            logger.info("getFactorStockNum|url:{}", url);
            String resJson =  HttpClientUtil.doGet(url);
            if (!StringUtils.isEmpty(resJson)) {
                resJson = HutoolUtil.unicode2Str(resJson);
            }
            return resJson;
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
        }
        return null;
    }

    /**
     * 获取新浪财经新闻列表
     * @param isProd
     * @return
     */
    public static String getNews(boolean isProd) {
        try {
            String url = getPythonUrl(isProd);
            url = url + "/get_news";
            logger.info("getNews|url:{}", url);
            String resJson =  HttpClientUtil.doGet(url);
            if (!StringUtils.isEmpty(resJson)) {
                resJson = HutoolUtil.unicode2Str(resJson);
            }
            return resJson;
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
        }
        return null;
    }

    /**
     * 获取华尔街见闻今天涨跌幅top50股票
     * @param isProd
     * @return
     */
    public static String getTop50Stock(boolean isProd, String orderType) {
        try {
            String url = getPythonUrl(isProd);
            url = url + "/get_top50_stock?order_type="+orderType;
            logger.info("getTop50Stock|url:{}", url);
            String resJson =  HttpClientUtil.doGet(url);
            if (!StringUtils.isEmpty(resJson)) {
                resJson = HutoolUtil.unicode2Str(resJson);
            }
            return resJson;
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
        }
        return null;
    }

    /**
     * 获取股票K线数据
     * @param isProd
     * @param ts_code
     * @param interval:1d/1w/1M
     * @param fqType:qfq/hfq/wfq
     * @param startDate:yyyy-MM-dd
     * @param endDate:yyyy-MM-dd
     * @return
     */
    public static String getStockKline(boolean isProd, String ts_code, String interval, String fqType, String startDate, String endDate) {
        if(StringUtils.isEmpty(ts_code)) {
            throw new RuntimeException("ts_code 不能为空");
        }
        if(StringUtils.isEmpty(interval)) {
            throw new RuntimeException("interval 不能为空");
        }
        if(StringUtils.isEmpty(startDate)) {
            throw new RuntimeException("startDate 不能为空");
        }
        if(StringUtils.isEmpty(endDate)) {
            throw new RuntimeException("endDate 不能为空");
        }

        try {
            String url = getPythonUrl(isProd);
            url = url + "/get_stock_kline?ts_code=" + ts_code + "&interval=" + interval + "&fq_type=" + fqType + "&start=" + startDate + "&end=" + endDate;
            logger.info("getStockKline|url:{}", url);
            String resJson =  HttpClientUtil.doGet(url);
            if (!StringUtils.isEmpty(resJson)) {
                resJson = HutoolUtil.unicode2Str(resJson);
            }
            return resJson;
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
        }
        return null;
    }

    /**
     * 获取港股K线数据
     * @param isProd
     * @param ts_code
     * @param interval:1d/1w/1M
     * @param fqType:qfq/hfq/wfq
     * @param startDate:yyyy-MM-dd
     * @param endDate:yyyy-MM-dd
     * @return
     */
    public static String getHkStockKline(boolean isProd, String ts_code, String interval, String fqType, String startDate, String endDate) {
        if(StringUtils.isEmpty(ts_code)) {
            throw new RuntimeException("ts_code 不能为空");
        }
        if(StringUtils.isEmpty(interval)) {
            throw new RuntimeException("interval 不能为空");
        }
        if(StringUtils.isEmpty(startDate)) {
            throw new RuntimeException("startDate 不能为空");
        }
        if(StringUtils.isEmpty(endDate)) {
            throw new RuntimeException("endDate 不能为空");
        }

        try {
            String url = getPythonUrl(isProd);
            url = url + "/get_hk_stock_kline?ts_code=" + ts_code + "&interval=" + interval + "&fq_type=" + fqType + "&start=" + startDate + "&end=" + endDate;
            logger.info("getHkStockKline|url:{}", url);
            String resJson =  HttpClientUtil.doGet(url);
            if (!StringUtils.isEmpty(resJson)) {
                resJson = HutoolUtil.unicode2Str(resJson);
            }
            return resJson;
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
        }
        return null;
    }

    /**
     * 获取美股K线数据
     * @param isProd
     * @param ts_code
     * @param interval:1d/1w/1M
     * @param fqType:qfq/hfq/wfq
     * @param startDate:yyyy-MM-dd
     * @param endDate:yyyy-MM-dd
     * @return
     */
    public static String getUsStockKline(boolean isProd, String ts_code, String interval, String fqType, String startDate, String endDate) {
        if(StringUtils.isEmpty(ts_code)) {
            throw new RuntimeException("ts_code 不能为空");
        }
        if(StringUtils.isEmpty(interval)) {
            throw new RuntimeException("interval 不能为空");
        }
        if(StringUtils.isEmpty(startDate)) {
            throw new RuntimeException("startDate 不能为空");
        }
        if(StringUtils.isEmpty(endDate)) {
            throw new RuntimeException("endDate 不能为空");
        }

        try {
            String url = getPythonUrl(isProd);
            url = url + "/get_us_stock_kline?ts_code=" + ts_code + "&interval=" + interval + "&fq_type=" + fqType + "&start=" + startDate + "&end=" + endDate;
            logger.info("getUsStockKline|url:{}", url);
            String resJson =  HttpClientUtil.doGet(url);
            if (!StringUtils.isEmpty(resJson)) {
                resJson = HutoolUtil.unicode2Str(resJson);
            }
            return resJson;
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
        }
        return null;
    }

    /**
     * 获取指数K线数据
     * @param isProd
     * @param ts_code
     * @param interval:1d/1w/1M
     * @param startDate:yyyy-MM-dd
     * @param endDate:yyyy-MM-dd
     * @return
     */
    public static String getIndexKline(boolean isProd, String ts_code, String interval, String startDate, String endDate) {
        if(StringUtils.isEmpty(ts_code)) {
            throw new RuntimeException("ts_code 不能为空");
        }
        if(StringUtils.isEmpty(interval)) {
            throw new RuntimeException("interval 不能为空");
        }
        if(StringUtils.isEmpty(startDate)) {
            throw new RuntimeException("startDate 不能为空");
        }
        if(StringUtils.isEmpty(endDate)) {
            throw new RuntimeException("endDate 不能为空");
        }

        try {
            String url = getPythonUrl(isProd);
            url = url + "/get_index_kline?ts_code=" + ts_code + "&interval=" + interval + "&start=" + startDate + "&end=" + endDate;
            logger.info("getIndexKline|url:{}", url);
            String resJson =  HttpClientUtil.doGet(url);
            if (!StringUtils.isEmpty(resJson)) {
                resJson = HutoolUtil.unicode2Str(resJson);
            }
            return resJson;
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
        }
        return null;
    }

    /**
     * 获取股票筹码分布图
     * @param isProd
     * @param ts_code
     * @param tradeDate:yyyy-MM-dd
     * @return
     */
    public static String getStockCyqChips(boolean isProd, String ts_code, String tradeDate) {
        if(StringUtils.isEmpty(ts_code)) {
            throw new RuntimeException("ts_code 不能为空");
        }
        if(StringUtils.isEmpty(tradeDate)) {
            throw new RuntimeException("tradeDate 不能为空");
        }

        try {
            String url = getPythonUrl(isProd);
            url = url + "/get_stock_cyq_chips?ts_code=" + ts_code + "&trade_date=" + tradeDate;
            logger.info("getStockCyqChips|url:{}", url);
            String resJson =  HttpClientUtil.doGet(url);
            if (!StringUtils.isEmpty(resJson)) {
                resJson = HutoolUtil.unicode2Str(resJson);
            }
            return resJson;
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
        }
        return null;
    }

    /**
     * 获取股票近10年周度/月度/季度收益率
     * @param isProd
     * @param ts_code
     * @param startDate:yyyy-MM-dd
     * @param endDate:yyyy-MM-dd
     * @return
     */
    public static String getStockWeekMonthQuarterPrList(boolean isProd, String ts_code, String startDate, String endDate, String type) {
        if(StringUtils.isEmpty(ts_code)) {
            throw new RuntimeException("ts_code 不能为空");
        }
        if(StringUtils.isEmpty(startDate)) {
            throw new RuntimeException("startDate 不能为空");
        }
        if(StringUtils.isEmpty(endDate)) {
            throw new RuntimeException("endDate 不能为空");
        }
        if(StringUtils.isEmpty(type)) {
            throw new RuntimeException("type 不能为空");
        }

        try {
            String url = getPythonUrl(isProd);
            if("week".equals(type)) {
                url = url + "/get_stock_week_pr_list?ts_code=" + ts_code + "&start=" + startDate + "&end=" + endDate;
            } else if ("month".equals(type)) {
                url = url + "/get_stock_month_pr_list?ts_code=" + ts_code + "&start=" + startDate + "&end=" + endDate;
            } else if ("quarter".equals(type)) {
                url = url + "/get_stock_quarter_pr_list?ts_code=" + ts_code + "&start=" + startDate + "&end=" + endDate;
            }
            logger.info("getStockWeekMonthQuarterPrList|url:{}", url);
            String resJson =  HttpClientUtil.doGet(url);
            if (!StringUtils.isEmpty(resJson)) {
                resJson = HutoolUtil.unicode2Str(resJson);
            }
            return resJson;
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
        }
        return null;
    }

    /**
     * 获取指数近10年周度/月度/季度收益率
     * @param isProd
     * @param ts_code
     * @param startDate:yyyy-MM-dd
     * @param endDate:yyyy-MM-dd
     * @return
     */
    public static String getIndexWeekMonthQuarterPrList(boolean isProd, String ts_code, String startDate, String endDate,
                                                        String intervalType, String indexType) {
        if(StringUtils.isEmpty(ts_code)) {
            throw new RuntimeException("ts_code 不能为空");
        }
        if(StringUtils.isEmpty(startDate)) {
            throw new RuntimeException("startDate 不能为空");
        }
        if(StringUtils.isEmpty(endDate)) {
            throw new RuntimeException("endDate 不能为空");
        }
        if(StringUtils.isEmpty(intervalType)) {
            throw new RuntimeException("intervalType 不能为空");
        }
        if(StringUtils.isEmpty(indexType)) {
            throw new RuntimeException("indexType 不能为空");
        }

        try {
            String url = getPythonUrl(isProd);
            if("week".equals(intervalType)) {
                url = url + "/get_index_week_pr_list?type=" + indexType + "&ts_code=" + ts_code + "&start=" + startDate + "&end=" + endDate;
            } else if ("month".equals(intervalType)) {
                url = url + "/get_index_month_pr_list?type=" + indexType + "&ts_code=" + ts_code + "&start=" + startDate + "&end=" + endDate;
            } else if ("quarter".equals(intervalType)) {
                url = url + "/get_index_quarter_pr_list?type=" + indexType + "&ts_code=" + ts_code + "&start=" + startDate + "&end=" + endDate;
            }
            logger.info("getIndexWeekMonthQuarterPrList|url:{}", url);
            String resJson =  HttpClientUtil.doGet(url);
            if (!StringUtils.isEmpty(resJson)) {
                resJson = HutoolUtil.unicode2Str(resJson);
            }
            return resJson;
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
        }
        return null;
    }

    /**
     * 获取可转债行情信息
     * @param isProd
     * @param ts_code
     * @return
     */
    public static String getCbDaily(boolean isProd, String ts_code) {
        if(StringUtils.isEmpty(ts_code)) {
            throw new RuntimeException("ts_code 不能为空");
        }

        try {
            String url = getPythonUrl(isProd);
            url = url + "/get_cb_daily?ts_code=" + ts_code;
            logger.info("getCbDaily|url:{}", url);
            String resJson =  HttpClientUtil.doGet(url);
            if (!StringUtils.isEmpty(resJson)) {
                resJson = HutoolUtil.unicode2Str(resJson);
            }
            return resJson;
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
        }
        return null;
    }

    public static String getComponentTxLogList(boolean isProd, Long combId, Short type, String tsCode) {
        try {
            String url = getPythonUrl(isProd);
            url = url + "/get_component_txlog_list?comb_id=" + combId + "&type=" + type + "&ts_code=" + tsCode;
            logger.info("getComponentTxLogList|url:{}", url);
            String resJson =  HttpClientUtil.doGet(url);
            if (!StringUtils.isEmpty(resJson)) {
                resJson = HutoolUtil.unicode2Str(resJson);
            }
            return resJson;
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
        }
        return null;
    }

    /**
     * 获取美股基本信息
     * @param isProd
     * @param ts_code
     * @return
     */
    public static String getUsStockBasic(boolean isProd, String ts_code, String data_from) {
        if(StringUtils.isEmpty(ts_code)) {
            throw new RuntimeException("ts_code 不能为空");
        }

        try {
            String url = getPythonUrl(isProd);
            url = url + "/get_us_stock_basic?ts_code=" + ts_code + "&data_from=" + data_from;
            logger.info("getUsStockBasic|url:{}", url);
            String resJson =  HttpClientUtil.doGet(url);
            if (!StringUtils.isEmpty(resJson)) {
                resJson = HutoolUtil.unicode2Str(resJson);
            }
            return resJson;
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
        }
        return null;
    }

    /**
     * 获取美股基本信息
     * @param isProd
     * @param symbol_em
     * @return
     */
    public static String getUsStockBasicV2(boolean isProd, String symbol_em) {
        if(StringUtils.isEmpty(symbol_em)) {
            throw new RuntimeException("symbol_em 不能为空");
        }

        try {
            String url = getPythonUrl(isProd);
            url = url + "/get_us_stock_basic_v2?symbol_em=" + symbol_em;
            logger.info("getUsStockBasicV2|url:{}", url);
            String resJson =  HttpClientUtil.doGet(url);
            if (!StringUtils.isEmpty(resJson)) {
                resJson = HutoolUtil.unicode2Str(resJson);
            }
            return resJson;
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
        }
        return null;
    }

    /**
     * 获取股票β系数曲线
     * @param isProd
     * @param ts_code
     * @return
     */
    public static String getStockBetaLine(boolean isProd, String ts_code) {
        if(StringUtils.isEmpty(ts_code)) {
            throw new RuntimeException("ts_code 不能为空");
        }

        try {
            String url = getPythonUrl(isProd);
            url = url + "/get_stock_beta_line?ts_code=" + ts_code;
            logger.info("getStockBetaLine|url:{}", url);
            String resJson =  HttpClientUtil.doGet(url);
            if (!StringUtils.isEmpty(resJson)) {
                resJson = HutoolUtil.unicode2Str(resJson);
            }
            return resJson;
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
        }
        return null;
    }

    /**
     * 获取股票预测价格
     * @param isProd
     * @param ts_code
     * @return
     */
    public static String getStockEstimatePrice(boolean isProd, String ts_code) {
        if(StringUtils.isEmpty(ts_code)) {
            throw new RuntimeException("ts_code 不能为空");
        }

        try {
            String url = getPythonUrl(isProd);
            url = url + "/get_stock_estimate_price?ts_code=" + ts_code;
            logger.info("getStockEstimatePrice|url:{}", url);
            String resJson =  HttpClientUtil.doGet(url);
            if (!StringUtils.isEmpty(resJson)) {
                resJson = HutoolUtil.unicode2Str(resJson);
            }
            return resJson;
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
        }
        return null;
    }

    /**
     * 获取股票/指数最大回撤
     * @param isProd
     * @param ts_code
     * @return
     */
    public static String getMaxDrawdown(boolean isProd, String type, String ts_code) {
        if(StringUtils.isEmpty(ts_code)) {
            throw new RuntimeException("ts_code 不能为空");
        }

        try {
            String url = getPythonUrl(isProd);
            url = url + "/get_max_draw_down?type="+type+"&ts_code=" + ts_code;
            logger.info("getMaxDrawdown|url:{}", url);
            String resJson =  HttpClientUtil.doGet(url);
            if (!StringUtils.isEmpty(resJson)) {
                resJson = HutoolUtil.unicode2Str(resJson);
            }
            return resJson;
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
        }
        return null;
    }

    public static String getFamousFundPos(boolean isProd, String fund_code) {
        try {
            String url = getPythonUrl(isProd);
            url = url + "/get_famous_fund_pos?fund_code=" + fund_code;
            logger.info("getFamousFundPos|url:{}", url);
            String resJson =  HttpClientUtil.doGet(url);
            if (!StringUtils.isEmpty(resJson)) {
                resJson = HutoolUtil.unicode2Str(resJson);
            }
            return resJson;
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
        }
        return null;
    }

    public static String getCoinStrategyList(boolean isProd) {
        try {
            String url = getPythonUrl(isProd);
            url = url + "/get_coin_strategy_list";
            logger.info("getCoinStrategyList|url:{}", url);
            String resJson =  HttpClientUtil.doGet(url);
            if (!StringUtils.isEmpty(resJson)) {
                resJson = HutoolUtil.unicode2Str(resJson);
            }
            return resJson;
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
        }
        return null;
    }

    public static String getCoinStrategyPrList(boolean isProd, String strategyId) {
        try {
            String url = getPythonUrl(isProd);
            url = url + "/get_coin_strategy_pr_list?strategy_id=" + strategyId;
            logger.info("getCoinStrategyPrList|url:{}", url);
            String resJson =  HttpClientUtil.doGet(url);
            if (!StringUtils.isEmpty(resJson)) {
                resJson = HutoolUtil.unicode2Str(resJson);
            }
            return resJson;
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
        }
        return null;
    }

    public static String getCoinHoldingList(boolean isProd, String strategyId) {
        try {
            String url = getPythonUrl(isProd);
            url = url + "/get_coin_holding_list?strategy_id=" + strategyId;
            logger.info("getCoinHoldingList|url:{}", url);
            String resJson =  HttpClientUtil.doGet(url);
            if (!StringUtils.isEmpty(resJson)) {
                resJson = HutoolUtil.unicode2Str(resJson);
            }
            return resJson;
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
        }
        return null;
    }

    public static String getCoinHoldHisList(boolean isProd, String strategyId) {
        try {
            String url = getPythonUrl(isProd);
            url = url + "/get_coin_hold_his?strategy_id=" + strategyId;
            logger.info("getCoinHoldHisList|url:{}", url);
            String resJson =  HttpClientUtil.doGet(url);
            if (!StringUtils.isEmpty(resJson)) {
                resJson = HutoolUtil.unicode2Str(resJson);
            }
            return resJson;
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
        }
        return null;
    }

}
